Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit cec98d332689dc647e0ee7067d73bfe8dbae1120


Parents : e3e92b5
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Signature : T66BB85Valid, signed by author
Date : 2026-07-25T17:09:32+02:00

Improved telephony output buffer handling

Changes

2 files changed, 26 insertions(+), 3 deletions(-)

M LXST/Sinks.py +13 -3

Diff

diff --git a/LXST/Primitives/Telephony.py b/LXST/Primitives/Telephony.py
index d36a997..35a0e36 100644
--- a/LXST/Primitives/Telephony.py
+++ b/LXST/Primitives/Telephony.py
@@ -606,6 +606,7 @@ class Telephone(SignallingReceiver):
if not from_signalling: self.signal(Signalling.PREFERRED_PROFILE+self.active_call.profile, self.active_call)
self.__reconfigure_transmit_pipeline()
self.receive_mixer.set_source_max_frames(self.active_call.audio_source, self.target_buffer_frames)
+ self.__update_output_buffer_targets()
def __select_call_profile(self, profile=None):
if profile == None: profile = Profiles.DEFAULT_PROFILE
@@ -637,6 +638,17 @@ class Telephone(SignallingReceiver):
self.receive_mixer = None
self.__prepare_dialling_pipelines()
+ def __update_output_buffer_targets(self):
+ if self.audio_output and self.target_buffer_frames:
+ if self.audio_output.buffer_max_height != self.target_buffer_frames:
+ self.audio_output.buffer_max_height = self.target_buffer_frames+1
+ RNS.log(f"Updated {self.audio_output} buffer size to {self.audio_output.buffer_max_height} frames", RNS.LOG_DEBUG)
+ if self.audio_output.autostart_min != self.target_buffer_frames:
+ self.audio_output.autostart_min = max(1, self.target_buffer_frames-2)
+ RNS.log(f"Updated {self.audio_output} buffer trigger height to {self.audio_output.autostart_min} frames", RNS.LOG_DEBUG)
+ self.audio_output.streaming = True
+ self.audio_output.wait_for_frames()
+
def __prepare_dialling_pipelines(self):
self.__select_call_profile(self.active_call.profile)
self.__select_call_mode(self.active_call.call_mode)
@@ -644,6 +656,7 @@ class Telephone(SignallingReceiver):
if self.receive_mixer == None: self.receive_mixer = Mixer(target_frame_ms=self.target_frame_time_ms, gain=self.receive_gain)
if self.dial_tone == None: self.dial_tone = ToneSource(frequency=self.dial_tone_frequency, gain=0.0, ease_time_ms=self.dial_tone_ease_ms, target_frame_ms=self.target_frame_time_ms, codec=Null(), sink=self.receive_mixer)
if self.receive_pipeline == None: self.receive_pipeline = Pipeline(source=self.receive_mixer, codec=Null(), sink=self.audio_output)
+ self.__update_output_buffer_targets()
def __update_audio_output(self):
if self.active_call and self.audio_output and self.receive_pipeline and self.receive_mixer:

diff --git a/LXST/Sinks.py b/LXST/Sinks.py
index 21fca3c..586ee85 100644
--- a/LXST/Sinks.py
+++ b/LXST/Sinks.py
@@ -145,6 +145,7 @@ class LineSink(LocalSink):
self.autostart_min = self.AUTOSTART_MIN
self.buffer_max_height = self.MAX_FRAMES-3
self.low_latency = low_latency
+ self.streaming = False
self.preferred_samplerate = Backend.SAMPLERATE
self.backend = Backend(preferred_device=self.preferred_device, samplerate=self.preferred_samplerate)
@@ -173,7 +174,9 @@ class LineSink(LocalSink):
RNS.log(f"{self} starting at {self.samples_per_frame} samples per frame, {self.channels} channels", RNS.LOG_DEBUG)
if self.autodigest and not self.should_run:
- if len(self.frame_deque) >= self.autostart_min: self.start()
+ if len(self.frame_deque) >= self.autostart_min:
+ RNS.log(f"Auto-digest buffer height of {self.autostart_min} reached on {self}, starting", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
+ self.start()
def start(self):
if not self.should_run:
@@ -184,6 +187,12 @@ class LineSink(LocalSink):
def stop(self):
self.should_run = False
+ def wait_for_frames(self):
+ if len(self.frame_deque) < self.autostart_min:
+ self.stop()
+ self.autodigest = True
+ RNS.log(f"{self} waiting for frames before re-engaging output", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
+
def enable_low_latency(self):
self.__wants_low_latency = True
@@ -209,7 +218,7 @@ class LineSink(LocalSink):
player.play(frame)
if len(self.frame_deque) > self.buffer_max_height:
- RNS.log(f"Buffer lag on {self} (height {len(self.frame_deque)}), dropping one frame", RNS.LOG_DEBUG)
+ RNS.log(f"Buffer lag on {self} (height {len(self.frame_deque)}), dropping one frame", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
self.frame_deque.popleft()
else:
@@ -217,6 +226,7 @@ class LineSink(LocalSink):
# TODO: Remove debug
# RNS.log(f"Buffer underrun on {self}", RNS.LOG_DEBUG)
self.underrun_at = time.time()
+ if self.streaming: time.sleep((self.frame_time*self.buffer_max_height)/3.0)
else:
if time.time() > self.underrun_at+(self.frame_time*self.frame_timeout):
RNS.log(f"No frames available on {self}, stopping playback", RNS.LOG_DEBUG)
@@ -341,7 +351,7 @@ class OpusFileSink(LocalSink):
frame = np.hstack([frame, frame[:, -1:]])
if frame.shape[0] < self.samples_per_frame:
- RNS.log("Insufficient frame data, padding with silence", RNS.LOG_DEBUG)
+ RNS.log("Insufficient frame data, padding with silence", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
silence_frame = np.zeros((self.samples_per_frame-frame.shape[0], frame.shape[1]), dtype=frame.dtype)
frame = np.vstack([frame, silence_frame])


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────